#import "DBAccurateAsyncTask.h"
#include <sys/time.h>
#include <unistd.h>
#include <pthread.h>

@interface DBAccurateAsyncTask()

@property (nonatomic, assign) BOOL needCancel;

@property (nonatomic, assign) uint64_t startStamp;

@end

@implementation DBAccurateAsyncTask {
    pthread_mutex_t mutex;//锁
    pthread_cond_t condi;//信号量
}

-(instancetype)init {
    if (self = [super init]) {
        /// 当前时间
        struct timeval startTimeVal;
        gettimeofday(&startTimeVal, NULL);
        self.startStamp = ((uint64_t)startTimeVal.tv_sec)*1000*1000+startTimeVal.tv_usec;
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&condi, NULL);
        self.needCancel = NO;
    }
    return self;
}

-(void)asyncTask:(dispatch_block_t)block afterDelay:(uint64_t)delay queue:(dispatch_queue_t)queue {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self msleep:[DBAccurateAsyncTask stampToSpec:self.startStamp + delay]];
        if (self.needCancel == NO) {
            if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {
                block();
            } else {
                dispatch_async(queue, block);
            }
        }
    });
}

-(void)sleepDelay:(uint64_t)delay {
    [self msleep:[DBAccurateAsyncTask stampToSpec:self.startStamp + delay]];
}

-(void)cancelTask {
    self.needCancel = YES;
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&condi);
    pthread_mutex_unlock(&mutex);
}

-(void)msleep:(struct timespec)spec {
    pthread_mutex_lock(&mutex);
    pthread_cond_timedwait(&condi, &mutex,&spec);
    pthread_mutex_unlock(&mutex);
}

+(struct timespec)stampToSpec:(uint64_t)stamp {
    struct timespec spec;
    spec.tv_nsec = (stamp % 1000000) * 1000;//纳秒
    spec.tv_sec = (stamp - (stamp % 1000000)) / 1000000;
    return spec;
}

+(DBAccurateAsyncTask *)asyncTask:(dispatch_block_t)block afterDelay:(uint64_t)delay queue:(dispatch_queue_t)queue {
    DBAccurateAsyncTask *task = [[DBAccurateAsyncTask alloc]init];
    [task asyncTask:block afterDelay:delay * 1000 queue:queue];
    return task;
}

+(void)msleep:(uint64_t)delay {
    if (delay > 0) {
        pthread_mutex_t mutex;//锁
        pthread_cond_t condi;//信号量
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&condi, NULL);
        uint64_t afterDur = delay * 1000;
        struct timeval afterTimeVal;
        gettimeofday(&afterTimeVal, NULL);//获取当前时间
        uint64_t afterStamp = ((uint64_t)afterTimeVal.tv_sec)*1000*1000+afterTimeVal.tv_usec;//精度微秒
        afterStamp = afterStamp+afterDur;
        pthread_mutex_lock(&mutex);
        struct timespec _mtimespec;
        _mtimespec.tv_nsec = (afterStamp % 1000000) * 1000;//纳秒
        _mtimespec.tv_sec = (afterStamp - (afterStamp % 1000000)) / 1000000;
        pthread_cond_timedwait(&condi, &mutex,&_mtimespec);
        pthread_mutex_unlock(&mutex);
    }
}

+(void)usleep:(uint64_t)delay {
    if (delay > 0) {
        pthread_mutex_t mutex;//锁
        pthread_cond_t condi;//信号量
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&condi, NULL);
        uint64_t afterDur = delay;
        struct timeval afterTimeVal;
        gettimeofday(&afterTimeVal, NULL);//获取当前时间
        uint64_t afterStamp = ((uint64_t)afterTimeVal.tv_sec)*1000*1000+afterTimeVal.tv_usec;//精度微秒
        afterStamp = afterStamp+afterDur;
        pthread_mutex_lock(&mutex);
        struct timespec _mtimespec;
        _mtimespec.tv_nsec = (afterStamp % 1000000) * 1000;//纳秒
        _mtimespec.tv_sec = (afterStamp - (afterStamp % 1000000)) / 1000000;
        pthread_cond_timedwait(&condi, &mutex,&_mtimespec);
        pthread_mutex_unlock(&mutex);
    }
}

+(DBAccurateAsyncTask *)timer:(dispatch_block_t)block rightOff:(BOOL)rightOff repeatCount:(int)repeatCount afterDelay:(uint64_t)delay queue:(dispatch_queue_t)queue {
    DBAccurateAsyncTask *task = [[DBAccurateAsyncTask alloc]init];
    __block uint64_t taskTime = rightOff ? 0 : delay;
    __block int _repeatCount = repeatCount;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (task.needCancel == NO && _repeatCount != 0) {
            [task _msleepTo:task.startStamp delay:taskTime];
            if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {
                block();
            } else {
                dispatch_async(queue, block);
            }
            taskTime += delay;
            if (_repeatCount > 0) {
                _repeatCount = _repeatCount - 1;
            }
        }
        task.needCancel = YES;
    });
    return task;
}

-(void)_msleepTo:(uint64_t)timeVal delay:(uint64_t)delay {
    uint64_t afterDur = delay * 1000;
    /// 计算要休眠到的时间
    uint64_t delayTime = timeVal+afterDur;
    struct timeval nowTimeVal;
    gettimeofday(&nowTimeVal, NULL);
    /// 计算当前时间和要休眠到的时间的差值
    uint64_t usleepTime = delayTime - (((uint64_t)nowTimeVal.tv_sec)*1000*1000+nowTimeVal.tv_usec);
    if (usleepTime > 5) {
        pthread_mutex_lock(&mutex);
        struct timespec _mtimespec;
        _mtimespec.tv_nsec = (delayTime % 1000000) * 1000;//纳秒
        _mtimespec.tv_sec = (delayTime - (delayTime % 1000000)) / 1000000;
        pthread_cond_timedwait(&condi, &mutex,&_mtimespec);
        pthread_mutex_unlock(&mutex);
    }
}

+(void)msleepTo:(uint64_t)timeVal delay:(uint64_t)delay {
    pthread_mutex_t mutex;//锁
    pthread_cond_t condi;//信号量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condi, NULL);
    uint64_t afterDur = delay * 1000;
    /// 计算要休眠到的时间
    uint64_t delayTime = timeVal+afterDur;
    struct timeval nowTimeVal;
    gettimeofday(&nowTimeVal, NULL);
    /// 计算当前时间和要休眠到的时间的差值
    uint64_t usleepTime = delayTime - (((uint64_t)nowTimeVal.tv_sec)*1000*1000+nowTimeVal.tv_usec);
    if (usleepTime > 5) {
        pthread_mutex_lock(&mutex);
        struct timespec _mtimespec;
        _mtimespec.tv_nsec = (delayTime % 1000000) * 1000;//纳秒
        _mtimespec.tv_sec = (delayTime - (delayTime % 1000000)) / 1000000;
        pthread_cond_timedwait(&condi, &mutex,&_mtimespec);
        pthread_mutex_unlock(&mutex);
    }
}

@end
